local happy = require "happy"
local lanes = require "lanes"
lanes.configure{ with_timers = false }

-------------------------------------------------------------------------------
---! CREATE LINDA TO HOLD MESSAGE QUEUE
-------------------------------------------------------------------------------
local linda = lanes.linda()

-------------------------------------------------------------------------------
---! CREATE TABLE TO HOLD SERVICES
-------------------------------------------------------------------------------
local service_map = {}

-------------------------------------------------------------------------------
---! CREATE TABLE TO HOLD THREADS
-------------------------------------------------------------------------------
local threads = {}

-------------------------------------------------------------------------------
---! CREATE TABLE TO HOLD RUNNING
-------------------------------------------------------------------------------
local running = false

-------------------------------------------------------------------------------
---! 内部方法
-------------------------------------------------------------------------------

local _create_thread = lanes.gen("*", function(func, ...)
    require "utils.preload"
    xpcall(func, error_traceback, ...)
end)

local _destory_thread = function(name, ...)
    local thread_lst = threads[name]
    if not thread_lst then
        return
    end

    threads[name] = nil

    for _, thread in ipairs(thread_lst) do
        thread:cancel(...)
    end

    for _, thread in ipairs(thread_lst) do
        thread:join()
    end
end

-------------------------------------------------------------------------------
---! 对外接口
-------------------------------------------------------------------------------
SERVICE_D = {}

function SERVICE_D:mainloop(seconds_)
    self.seconds_ = seconds_ or 3.0

    running = true
    happy.loop(function()
        self.service_channels = self.service_channels or table.keys(service_map)
        if #self.service_channels <= 0 then
            lanes.sleep(seconds_)
            return
        end

        local name, data = linda:receive(seconds_, table.unpack(self.service_channels))
        if not name then
            return
        end

        if SERVICE_D:dispatch(name, data) then
            return
        end

        spdlog.warn("service", string.format("linda recvive name = %s undefined, data = %s", name, data))
    end)

    happy.exit(function()
        running = false

        spdlog.debug("service", "start post dest ...")
        local tick = os.clock()
        post_dest()
        local cost = os.clock() - tick
        spdlog.debug("service", "finish dest cost tick = %s", cost)

        spdlog.debug("service", "start exit service ...")
        local tick = os.clock()
        SERVICE_D:exit()
        local cost = os.clock() - tick
        spdlog.debug("service", "finish exit service cost tick = %s", cost)
    end)
end

function SERVICE_D:exit(mode)
    mode = mode or "soft"

    ---! 依次关闭指定线程处理
    _destory_thread("redis", mode, 5.0, false)
    _destory_thread("mysql", mode, 5.0, false)
    _destory_thread("timer", mode, 5.0, false)

    ---! 再关闭其他非指定线程
    for name, _ in pairs(threads) do
        if name ~= "repl" then
            _destory_thread(name, mode, 5.0, false)
        end
    end

    ---! 最后强制关闭REPL模块
    _destory_thread("repl", "hard", 0, true)

    ---! 重置线程表
    threads = {}
end

function SERVICE_D:is_running()
    return running
end

function SERVICE_D:register(name, func)
    if service_map[name] then
        if self.service_channels then
            self.service_channels = nil
        end
    end
    service_map[name] = func
end

function SERVICE_D:dispatch(name, data)
    local func = service_map[name]
    if not func then
        return false
    end

    if name ~= "coroutine_resume" then
        THREAD_D:create(function()
            return func(linda, table.unpack(data))
        end)
    else
        func(linda, table.unpack(data))
    end
    return true
end

function SERVICE_D:create(name, func, ...)
    local thread = _create_thread(func, linda, ...)
    local thread_lst = threads[name]
    if not thread_lst then
        threads[name] = { thread }
    else
        table.insert(threads[name], thread)
    end
end

function SERVICE_D:post(channel, ...)
    return linda:send(channel, table.pack(...))
end
